package com.sky.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.mybatis.mp.core.mybatis.mapper.context.Pager;
import cn.mybatis.mp.core.sql.executor.chain.QueryChain;
import com.github.pagehelper.PageHelper;
import com.sky.constant.JwtClaimsConstant;
import com.sky.constant.MessageConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.BaseContext;
import com.sky.dto.AdminUserPageQueryDTO;
import com.sky.dto.LoginDTO;
import com.sky.dto.PageQueryDTO;
import com.sky.entity.*;
import com.sky.exception.BaseException;
import com.sky.mapper.*;
import com.sky.properties.JwtProperties;
import com.sky.service.AdminUserService;
import com.sky.service.AdminRoleService;
import com.sky.utils.JwtUtil;
import com.sky.utils.Md5Util;
import com.sky.vo.AdminLoginVO;
import com.sky.vo.AdminUserPageVO;
import db.sql.api.cmd.JoinMode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author majun
 * @package com.sky.service.impl
 * @date 2024/11/21 12:56
 */
@Slf4j
@Service
public class AdminUserServiceImpl implements AdminUserService {

    @Autowired
    private AdminsMapper adminsMapper;

    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private AdminRolesMapper adminRolesMapper;

    @Autowired
    private AdminRolePermissionsMapper adminRolePermissionsMapper;

    @Autowired
    private AdminRoleAssignmentsMapper adminRoleAssignmentsMapper;

    @Autowired
    private AdminPermissionsMapper adminPermissionsMapper;

    @Autowired
    private AdminRoleService adminRoleService;

    /**
     * 管理员登录
     *
     * @param loginDTO 登录表单
     * @return adminToken
     */
    @Override
    public AdminLoginVO adminLogin(LoginDTO loginDTO) {

        AdminLoginVO adminLoginVO = new AdminLoginVO();

        //用户名密码为空，抛出异常
        if (loginDTO.getUsername().isEmpty() || loginDTO.getPassword().isEmpty()) {
            throw new BaseException(MessageConstant.LONGIN_Empty);
        }

        //数据库查询账号信息
        Admins admin = adminsMapper.get(where -> {
            where.eq(Admins::getUsername, loginDTO.getUsername());
        });

        //判断账户是否存在，不存在则抛出异常
        if (admin == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        //判断密码是否正确
        if (!admin.getPassword().equals(Md5Util.getMD5String(loginDTO.getPassword()))) {
            throw new BaseException(MessageConstant.PASSWORD_ERROR);
        }

        //先校验指定账号是否已被封禁，如果被封禁则抛出异常 `DisableServiceException`
        StpUtil.checkDisable(admin.getAdminId());

        //校验通过，权限登录
        StpUtil.login(admin.getAdminId());

        Map<String, Object> map = new HashMap<>();
        map.put(JwtClaimsConstant.ADMIN_ID, admin.getAdminId());

        //生成token
        String token = JwtUtil.createJWT(jwtProperties.getAdminSecretKey(), jwtProperties.getAdminTtl(), map);

        //向redis中存储token
        redisTemplate.opsForValue().set(token, token);
        //设置存储时间
        redisTemplate.expire(token, jwtProperties.getAdminTtl(), TimeUnit.MILLISECONDS);

        //更新账户最后登录时间
        admin.setLastLogin(new Date());

        //提交数据库
        adminsMapper.update(admin);

        //存入token
        adminLoginVO.setToken(token);

        //获取用户全部权限并存入adminLoginVO
        adminLoginVO.setAdminPermissionsList(getPermissionsByAdminId(admin.getAdminId().toString()));

        return adminLoginVO;

    }

    /**
     * 获取管理员角色全部权限
     *
     * @return 管理员角色全部权限
     */
    public List<String> getPermissionsByAdminId(String adminId) {

        List<String> adminpermissionsList = new ArrayList<>();

        //通过AdminId查询关联角色Id
        List<AdminRoleAssignments> adminRoleAssignmentsList = adminRoleAssignmentsMapper.list(where -> {
            where.like(AdminRoleAssignments::getAdminId, adminId.toString());
        });

        //创建列表存储用户全部权限
        List<AdminRolePermissions> adminRolePermissionsList = new ArrayList<>();

        //通过角色ID列表遍历查询角色关联的所有权限ID
        for (AdminRoleAssignments adminRoleAssignments : adminRoleAssignmentsList) {

            List<AdminRolePermissions> adminRolePermissions = adminRolePermissionsMapper.list(where -> {
                where.like(AdminRolePermissions::getRoleId, String.valueOf(adminRoleAssignments.getRoleId()));
            });

            adminRolePermissionsList.addAll(adminRolePermissions);

        }

        //遍历查询角色对应的权限名称
        for (AdminRolePermissions adminRolePermissions : adminRolePermissionsList) {

            AdminPermissions adminPermissions = adminPermissionsMapper.get(where -> {
                where.like(AdminPermissions::getPermissionId, String.valueOf(adminRolePermissions.getPermissionId()));
            });

            adminpermissionsList.add(adminPermissions.getPermissionName());

        }

        return adminpermissionsList;

    }

    /**
     * 获取管理员角色
     *
     * @return 管理员角色
     */
    public List<String> getAdminRoles(String adminId) {

        List<String> adminRolesList = new ArrayList<>();

        //通过AdminId查询关联角色Id
        List<AdminRoleAssignments> adminRoleAssignmentsList = adminRoleAssignmentsMapper.list(where -> {
            where.like(AdminRoleAssignments::getAdminId, adminId.toString());
        });

        //通过角色Id查询角色详细信息列表
        for (AdminRoleAssignments adminRoleAssignments : adminRoleAssignmentsList) {

            AdminRoles adminRoles = adminRolesMapper.get(where -> {
                //修改了like方发为eq精准匹配
                where.eq(AdminRoles::getRoleId, String.valueOf(adminRoleAssignments.getRoleId()));
            });

            adminRolesList.add(adminRoles.getRoleName());

        }

        //循环遍历获取角色名

        return adminRolesList;

    }

    /**
     * 获取管理员详细信息
     *
     * @param adminId 管理员ID
     * @return 管理员信息
     */
    @Override
    public Admins getAdminDetails(Integer adminId) {
        // 查询管理员信息
        Admins admin = adminsMapper.get(where -> where.eq(Admins::getAdminId, adminId));

        if (admin == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        // 出于安全考虑,清除密码信息
        admin.setPassword(null);

        return admin;
    }

    /**
     * 创建新的管理员账户
     * 1. 检查用户名是否已存在，避免重复
     * 2. 设置账户默认状态为启用
     * 3. 设置创建时间
     * 4. 对密码进行MD5加密存储
     * 5. 保存管理员基本信息
     * 6. 如果提供了角色信息，建立管理员与角色的关联关系
     *
     * @param admin   管理员信息对象，包含用户名、密码等信息
     * @param roleIds 要分配的角色ID列表
     * @throws BaseException 当用户名已存在时抛出异常
     */
    @Override
    @Transactional
    public void createAdmin(Admins admin, List<Integer> roleIds) {
        // 检查用户名是否已存在
        Admins existingAdmin = adminsMapper.get(where -> {
            where.eq(Admins::getUsername, admin.getUsername());
        });
        if (existingAdmin != null) {
            throw new BaseException("用户名已存在");
        }

        // 设置默认状态为启用
        admin.setStatus(0); // 0表示正常状态
        // 设置创建时间
        admin.setCreationDate(new Date());
        // 密码加密
        admin.setPassword(Md5Util.getMD5String(admin.getPassword()));

        // 保存管理员信息
        adminsMapper.save(admin);

        // 如果有角色信息，保存角色关联
        if (roleIds != null && !roleIds.isEmpty()) {
            for (Integer roleId : roleIds) {
                AdminRoleAssignments roleAssignment = new AdminRoleAssignments();
                roleAssignment.setAdminId(admin.getAdminId());
                roleAssignment.setRoleId(roleId);
                adminRoleAssignmentsMapper.save(roleAssignment);
            }
        }
    }

    /**
     * 删除指定管理员账户
     * 1. 检查管理员是否存在
     * 2. 删除管理员基本信息
     * 3. 删除管理员与角色的关联关系
     *
     * @param adminId 要删除的管理员ID
     * @throws BaseException 当管理员不存在时抛出异常
     */
    @Override
    @Transactional
    public void deleteAdmin(Integer adminId) {
        // 检查管理员是否存在
        Admins admin = adminsMapper.get(where -> where.eq(Admins::getAdminId, adminId));
        if (admin == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        // 先删除角色关联
        adminRoleAssignmentsMapper.delete(where -> where.eq(AdminRoleAssignments::getAdminId, adminId));

        // 再删除管理员信息
        adminsMapper.delete(where -> where.eq(Admins::getAdminId, adminId));
    }

    /**
     * 更新管理员信息
     * 1. 检查要更新的管理员是否存在
     * 2. 如果修改了用户名，检查新用户名是否与其他账户冲突
     * 3. 如果提供了新密码，对新密码进行加密
     * 4. 更新管理员基本信息
     * 5. 如果更新了角色信息，重新建立角色关联
     *
     * @param admin   更新的管理员信息，包含管理员ID和要更新的字段
     * @param roleIds 要更新的角色ID列表
     * @throws BaseException 当管理员不存在或新用户名已存在时抛出异常
     */
    @Override
    @Transactional
    public void updateAdmin(Admins admin, List<Integer> roleIds) {
        // 检查管理员是否存在
        Admins existingAdmin = adminsMapper.get(where -> where.eq(Admins::getAdminId, admin.getAdminId()));
        if (existingAdmin == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        // 如果修改了用户名，检查新用户名是否已存在
        if (admin.getUsername() != null && !admin.getUsername().equals(existingAdmin.getUsername())) {
            Admins adminWithSameUsername = adminsMapper.get(where -> {
                where.eq(Admins::getUsername, admin.getUsername());
            });
            if (adminWithSameUsername != null) {
                throw new BaseException("用户名已存在");
            }
        }

        // 如果密码不为空，进行加密
        if (admin.getPassword() != null && !admin.getPassword().isEmpty()) {
            admin.setPassword(Md5Util.getMD5String(admin.getPassword()));
        } else {
            admin.setPassword(null); // 不更新密码
        }

        // 更新管理员信息
        adminsMapper.update(admin);

        // 如果提供了角色信息，更新角色关联
        if (roleIds != null) {
            // 删除原有角色关联
            adminRoleAssignmentsMapper.delete(where -> where.eq(AdminRoleAssignments::getAdminId, admin.getAdminId()));
            // 添加新的角色关联
            for (Integer roleId : roleIds) {
                AdminRoleAssignments roleAssignment = new AdminRoleAssignments();
                roleAssignment.setAdminId(admin.getAdminId());
                roleAssignment.setRoleId(roleId);
                adminRoleAssignmentsMapper.save(roleAssignment);
            }
        }
    }

    /**
     * 重置管理员密码
     * 1. 验证管理员是否存在
     * 2. 对新密码进行MD5加密
     * 3. 更新密码和更新时间
     *
     * @param adminId     管理员ID
     * @param newPassword 新密码（明文，将在方法内进行加密）
     * @throws BaseException 当管理员不存在时抛出异常
     */
    @Override
    @Transactional
    public void resetPassword(Integer adminId, String newPassword) {
        // 检查管理员是否存在
        Admins admin = adminsMapper.get(where -> where.eq(Admins::getAdminId, adminId));
        if (admin == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        // 更新密码
        admin.setPassword(Md5Util.getMD5String(newPassword));
        adminsMapper.update(admin);
    }

    /**
     * 分页查询管理员列表
     * 1. 构建查询条件
     * 2. 如果提供了名称参数，添加模糊查询条件（匹配用户名或姓名）
     * 3. 按创建时间降序排序
     * 4. 执行分页查询
     *
     * @param pager 分页参数对象，包含页码、每页大小等信息
     * @param name  搜索关键字，可以匹配用户名或姓名（可选）
     * @return 分页结果，包含管理员列表和分页信息
     */
    @Override
    public Pager<Admins> pageQuery(PageQueryDTO pager, String name) {

        // 执行分页查询
        return QueryChain.of(adminsMapper)
                .ignoreEmptyInCondition(true)
                .ignoreNullValueInCondition(true)
                .trimStringInCondition(true)
                .like(Admins::getUsername, name)
                .orderBy(Admins::getCreationDate)
                .paging(Pager.of(pager.getPage(), pager.getPageSize()));
    }

    /**
     * 更新管理员账户状态（启用/禁用）
     * 1. 检查管理员是否存在
     * 2. 更新状态和更新时间
     * 3. 如果是禁用操作，使用Sa-Token进行永久封禁
     * 4. 如果是启用操作，解除Sa-Token的封禁状态
     *
     * @param adminId 管理员ID
     * @param status  目标状态：StatusConstant.ENABLE-启用，StatusConstant.DISABLE-禁用
     * @throws BaseException 当管理员不存在时抛出异常
     */
    @Override
    @Transactional
    public void updateStatus(Integer adminId, Integer status) {
        // 检查管理员是否存在
        Admins admin = adminsMapper.get(where -> where.eq(Admins::getAdminId, adminId));
        if (admin == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        // 更新状态
        admin.setStatus(status);
        adminsMapper.update(admin);

        // 如果是禁用状态，强制下线
        if (status == StatusConstant.DISABLE) {
            StpUtil.kickout(adminId); //先踢下线
            StpUtil.disable(adminId, -1); // 永久封禁
        } else {
            StpUtil.untieDisable(adminId); // 解除封禁
        }
    }

    /**
     * 分页获取管理员账户列表
     * 该方法实现了按条件分页查询管理员账户信息的功能
     *
     * @param queryDTO 查询条件数据传输对象，包含：
     *                 - page: 当前页码
     *                 - pageSize: 每页显示数量
     *                 - username: 管理员用户名（可选，支持模糊查询）
     *                 - email: 邮箱地址（可选，支持模糊查询）
     *                 - role: 角色ID（可选，精确匹配）
     *                 - status: 账户状态（可选，精确匹配）
     * @return Pager<AdminUserPageVO> 分页结果，包含：
     * - records: 当前页的管理员列表数据
     * - total: 总记录数
     * - pages: 总页数
     * - size: 每页大小
     * - current: 当前页码
     * @throws BaseException 当查询过程中发生异常时抛出
     */
    @Override
    public Pager<AdminUserPageVO> getAdminUsersPage(AdminUserPageQueryDTO queryDTO) {
        // 1. 使用QueryChain执行分页查询
        Pager<Admins> adminsPager = QueryChain.of(adminsMapper)
                // 1.1 设置条件处理行为
                .ignoreEmptyInCondition(true)          // 忽略空字符串条件
                .ignoreNullValueInCondition(true)      // 忽略null值条件
                .trimStringInCondition(true)           // 对字符串条件进行trim处理
                // 1.2 添加查询条件
                .like(Admins::getUsername, queryDTO.getUsername())    // 用户名模糊查询
                .like(Admins::getEmail, queryDTO.getEmail())         // 邮箱模糊查询
                .eq(Admins::getStatus, queryDTO.getStatus())        // 状态精确匹配
                // 1.3 设置排序
                .orderBy(Admins::getCreationDate)                    // 按创建时间排序
                // 1.4 执行分页查询
                .paging(Pager.of(queryDTO.getPage(), queryDTO.getPageSize()));

        // 2. 转换为VO对象
        List<AdminUserPageVO> voList = adminsPager.getResults().stream().map(admin -> {
            // 2.1 创建VO对象并复制基本属性
            AdminUserPageVO vo = new AdminUserPageVO();
            vo.setId(admin.getAdminId());
            vo.setUsername(admin.getUsername());
            vo.setEmail(admin.getEmail());
            vo.setCreationDate(admin.getCreationDate().toString());
            if (admin.getLastLogin() != null) {
                vo.setLastLogin(admin.getLastLogin().toString());
            }
            vo.setStatus(admin.getStatus());

            // 2.2 获取管理员的角色列表
            List<String> roles = getAdminRoles(admin.getAdminId().toString());

            // 2.3 如果有角色，设置第一个角色的信息
            if (!roles.isEmpty()) {
                // 根据角色名称查找对应的角色ID和名称
                AdminRoles role = adminRolesMapper.get(where ->
                        where.eq(AdminRoles::getRoleName, roles.get(0))
                );
                if (role != null) {
                    vo.setRole(role.getRoleId());
                    vo.setRoleName(role.getRoleName());
                }
            }

            return vo;
        }).collect(Collectors.toList());

        // 3. 如果指定了角色ID进行过滤，过滤掉不匹配的记录
        if (queryDTO.getRole() != null) {
            voList = voList.stream()
                    .filter(vo -> queryDTO.getRole().equals(vo.getRole()))
                    .collect(Collectors.toList());
        }

        // 4. 构建最终的分页结果
        Pager<AdminUserPageVO> resultPager = new Pager<>();
        resultPager.setTotal(adminsPager.getTotal());       // 设置总记录数
        resultPager.setResults(voList);                     // 设置当前页数据
        resultPager.setNumber(adminsPager.getNumber());       // 设置总页数
        resultPager.setSize(adminsPager.getSize());         // 设置每页大小

        return resultPager;
    }

    /**
     * 获取管理员个人资料
     *
     * @param adminId 管理员ID
     * @return Admins
     */
    public Admins getAdminById(Integer adminId) {

        if (adminId == null) {
            throw new BaseException(MessageConstant.THE_SUBMISSION_CANNOT_BE_EMPTY);
        }

        Admins admin = adminsMapper.get(where -> where.eq(Admins::getAdminId, adminId));

        if (admin == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        return admin;

    }

    /**
     * 更新管理员个人资料
     *
     * @param admin 管理员信息
     */
    public void adminUpdateAdmin(Admins admin) {

        adminsMapper.update(admin);

    }

    /**
     * 修改管理员密码
     * 1. 验证管理员是否存在
     * 2. 验证旧密码是否正确
     * 3. 验证新密码是否与旧密码相同
     * 4. 对新密码进行加密并更新
     *
     * @param adminId     管理员ID
     * @param oldPassword 当前密码
     * @param newPassword 新密码
     */
    @Override
    @Transactional
    public void updatePassword(Integer adminId, String oldPassword, String newPassword) {
        // 检查管理员是否存在
        Admins admin = adminsMapper.get(where -> where.eq(Admins::getAdminId, adminId));
        if (admin == null) {
            throw new BaseException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        // 验证旧密码是否正确
        if (!admin.getPassword().equals(Md5Util.getMD5String(oldPassword))) {
            throw new BaseException(MessageConstant.PASSWORD_ERROR);
        }

        // 验证新密码是否与旧密码相同
        if (oldPassword.equals(newPassword)) {
            throw new BaseException(MessageConstant.PASSWORD_SAME_AS_OLD);
        }

        try {
            // 更新密码
            admin.setPassword(Md5Util.getMD5String(newPassword));
            adminsMapper.update(admin);
        } catch (Exception e) {
            log.error("密码修改失败：", e);
            throw new BaseException(MessageConstant.PASSWORD_UPDATE_FAILED);
        }
    }
}
